const std = @import("std");
const Io = std.Io;

// https://zhuanlan.zhihu.com/p/570148970
pub const color = enum(u8) {
    reset = 0,
    bold = 1,
    italic = 3,
    underline = 4,

    red = 31,
    green = 32,
    yellow = 33,
    blue = 34,
    magenta = 35,
    cyan = 36,
    grey = 90,

    const Writer = std.Io.Writer;
    pub fn format(self: color, w: *Writer) Writer.Error!void {
        return self.with("").format(w);
    }

    /// Combines a color with text attributes for terminal output.
    pub fn with(self: color, attr: []const u8) WithAttr {
        return .{ .color = self, .attr = attr };
    }

    /// Holds a color with associated text attributes for ANSI escape sequence generation.
    pub const WithAttr = struct {
        color: color,
        attr: []const u8,

        pub fn format(self: WithAttr, w: *Writer) Writer.Error!void {
            // Control Sequence Introducer
            try w.writeAll("\x1b[");

            // Set graphics mode
            for (self.attr) |a| {
                const attr: color = switch (a) {
                    'r' => .reset,
                    'b' => .bold,
                    'i' => .italic,
                    'u' => .underline,
                    else => continue,
                };
                try w.print("{};", .{@intFromEnum(attr)});
            }
            try w.print("{}m", .{@intFromEnum(self.color)});
        }
    };
};

/// Fixed-size string buffer with NUL-termination when possible.
pub fn Name(comptime n: u16) type {
    return extern struct {
        data: [n]u8,

        // Ensure binary compatibility with simple byte arrays
        comptime {
            std.debug.assert(@sizeOf(Self) == @sizeOf([n]u8));
        }

        const Self = @This();
        pub const empty: Self = .init("");
        pub const root: Self = .init("/");
        pub const cwd: Self = .init(".");
        pub const parent: Self = .init("..");

        /// Initializes name from string, truncating and null-terminating as needed
        /// Handles both short strings (adds null) and full-length names (no null)
        pub fn init(name: []const u8) Self {
            var self: Self = undefined;
            self.set(name);
            return self;
        }

        /// Returns slice up to first null terminator or full buffer if none exists
        pub fn get(self: *const Self) []const u8 {
            return std.mem.sliceTo(&self.data, 0);
        }

        /// Sets name, truncates to fit and NUL-terminates if space allows
        pub fn set(self: *Self, name: []const u8) void {
            const len = @min(n, name.len);
            @memcpy(self.data[0..len], name[0..len]);
            if (len < n) self.data[len] = 0; // NULL terminated
        }

        /// Compares names using their logical string representations
        pub fn eql(self: Self, name: Self) bool {
            return std.mem.eql(u8, self.get(), name.get());
        }
    };
}

/// safe version of @intCast that returns an error instead of panicking
pub fn intCast(comptime T: type, int: anytype) !T {
    return std.math.cast(T, int) orelse error.IntegerOverflow;
}

pub fn typeFromInt(T: type, int: anytype) T {
    comptime std.debug.assert(@bitSizeOf(T) == @bitSizeOf(@TypeOf(int)));
    return switch (@typeInfo(T)) {
        .int => int,
        .@"enum" => @enumFromInt(int),
        .@"struct" => @bitCast(int),
        else => @compileError("unsupported type" ++ T),
    };
}

pub fn intFromType(value: anytype) if (@typeInfo(@TypeOf(value)) != .int)
    std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(value)))
else
    @TypeOf(value) {
    return switch (@typeInfo(@TypeOf(value))) {
        .int => value,
        .@"enum" => @intFromEnum(value),
        .@"struct" => @bitCast(value),
        else => |T| @compileError("unsupported type" ++ T),
    };
}

/// Generic MMIO (Memory-Mapped I/O) register access utility.
/// Supports both enum-based and string-based register addressing.
pub fn Mmio(e: type, comptime string: bool) type {
    return struct {
        /// Enum type defining register layout
        pub const Enum = e;
        /// Integer type used for raw register access (must match hardware register size)
        pub const Int = e.Int;
        /// Register identifier type (string or enum based on template parameter)
        pub const Reg = if (string) []const u8 else Enum;

        /// Base memory address of the MMIO region
        base: usize,

        const Self = @This();
        pub const empty: Self = .init(0);

        pub fn init(base: usize) Self {
            return .{ .base = base };
        }

        pub fn ptr(self: Self, comptime reg: Reg, offset: usize) [*]volatile u8 {
            const r = if (string) @field(Enum, reg) else reg;
            return @ptrFromInt(self.base + @intFromEnum(r) + offset);
        }

        /// Returns the type associated with a register (defaults to Int)
        /// Looks for a type declaration matching the TitleCase version of the register name
        fn Type(comptime self: Reg) type {
            const type_name = TitleCase(if (string) self else @tagName(self));
            return if (@hasDecl(Enum, type_name)) @field(Enum, type_name) else Int;
        }

        /// Returns a pointer to a register array (for consecutive registers)
        pub fn array(self: Self, comptime reg: Reg) [*]volatile Int {
            return @ptrCast(@alignCast(self.ptr(reg, 0)));
        }

        /// Sets bits in a register (atomic OR operation)
        pub fn set(self: Self, comptime reg: Reg, mask: Type(reg)) void {
            self.array(reg)[0] |= intFromType(mask);
        }

        /// Checks if specific bits are set in a register
        pub fn isSet(self: Self, comptime reg: Reg, mask: Type(reg)) bool {
            const value: Int = intFromType(mask);
            return self.array(reg)[0] & value == value;
        }

        /// Reads a register value with proper type conversion
        pub fn read(self: Self, comptime reg: Reg) Type(reg) {
            return typeFromInt(Type(reg), self.array(reg)[0]);
        }

        /// Writes a value to a register with proper type conversion
        pub fn write(self: Self, comptime reg: Reg, value: Type(reg)) void {
            self.array(reg)[0] = intFromType(value);
        }

        /// Reads an integer of type T from the specified memory-mapped register.
        /// For T sizes larger than Int, performs a multi-word read using the width of Int.
        /// For T sizes smaller than or equal to Int, performs a single read at the specified width.
        pub fn readInt(self: Self, comptime reg: Reg, offset: usize, T: type) T {
            const len = @bitSizeOf(T) / @bitSizeOf(Int);
            if (len > 1) {
                var vs: [len]Int = undefined;
                const registers: *volatile [len]Int = @ptrCast(@alignCast(self.ptr(reg, offset)));
                @memcpy(&vs, registers);
                return @bitCast(vs);
            } else {
                const register: *volatile T = @ptrCast(@alignCast(self.ptr(reg, offset)));
                return register.*;
            }
        }

        /// Writes an integer of type T to the specified memory-mapped register.
        /// For T sizes larger than Int, performs a multi-word write using the width of Int.
        /// For T sizes smaller than or equal to Int, performs a single write at the specified width.
        pub fn writeInt(self: Self, comptime reg: Reg, offset: usize, value: anytype) void {
            const T = @TypeOf(value);
            const len = @bitSizeOf(T) / @bitSizeOf(Int);
            if (len > 1) {
                const vs: [len]Int = @bitCast(value);
                const registers: *volatile [len]Int = @ptrCast(@alignCast(self.ptr(reg, offset)));
                @memcpy(registers, &vs);
            } else {
                const register: *volatile T = @ptrCast(@alignCast(self.ptr(reg, offset)));
                register.* = value;
            }
        }
    };
}

/// Converts snake_case string to TitleCase
pub fn TitleCase(comptime snake_case: []const u8) []const u8 {
    return comptime na: {
        var buffer: [snake_case.len]u8 = undefined;
        var i: usize = 0;
        var capitalize_next = true;

        for (snake_case) |c| {
            if (c == '_') {
                capitalize_next = true;
                continue;
            }

            if (capitalize_next) {
                buffer[i] = std.ascii.toUpper(c);
                capitalize_next = false;
            } else {
                buffer[i] = c;
            }
            i += 1;
        }

        break :na buffer[0..i];
    };
}

test TitleCase {
    try comptime std.testing.expect(std.mem.eql(u8, "SnakeCase", TitleCase("snake_case")));
}

/// Given a type, recursively references all the declarations inside, so that the semantic analyzer sees them.
/// For deep types, you may use `@setEvalBranchQuota`.
pub fn refAllDeclsRecursive(comptime T: type) void {
    inline for (comptime std.meta.declarations(T)) |decl| {
        if (@TypeOf(@field(T, decl.name)) == type) {
            switch (@typeInfo(@field(T, decl.name))) {
                .@"struct", .@"enum", .@"union", .@"opaque" => refAllDeclsRecursive(@field(T, decl.name)),
                else => {},
            }
        }
        _ = &@field(T, decl.name);
    }
}

/// Generic drain helper function for std.Io.Writer implementations.
pub fn drain(w: anytype, puts: anytype, io_w: *std.Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
    const buffered = io_w.buffered();
    if (buffered.len > 0) {
        _ = try puts(w, buffered);
        _ = io_w.consumeAll();
    }

    if (data.len == 0) return 0;
    var n: usize = 0;
    for (data[0 .. data.len - 1]) |buf| {
        if (buf.len == 0) continue;
        n += try puts(w, buf);
    }

    const pattern = data[data.len - 1];
    for (0..splat) |_| {
        n += try puts(w, pattern);
    }
    return n;
}

pub fn stream(r: anytype, gets: anytype, io_w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
    const buffer = limit.slice(try io_w.writableSliceGreedy(1));
    const n = try gets(r, buffer);
    io_w.advance(n);
    return n;
}
